package com.xiu.json.engine;

import com.xiu.json.exception.ParseJsonException;

import java.util.ArrayList;
import java.util.List;

/**
 * @Author: tjc
 * @description
 * @Date Created in 0:12 2022-04-03
 */
public class TokenParser {
    StringLoader loader;
    char curc;

    public TokenParser(String str) {
        loader = new StringLoader(str);
        curc = nextNotBlankC();
    }

    private Token parse() {
        if (curc != 0) {
            if (curc == '"') {
                return parseString();
            } else if (isNum(curc) || curc == '-') {
                return parseNum();
            } else if (CharacterDef.symbolSet.contains(curc)) {
                // 符号
                Token t = Token.symbol(String.valueOf(curc));
                nextc();
                return t;
            } else if (curc == 'n') {
                return parseNull();
            } else if (curc == 't' || curc == 'f') {
                return parseBool();
            } else if (CharacterDef.blankChars.contains(curc)) {
                nextNotBlankC();
                return parse();
            } else {
                throw new ParseJsonException(String.format("unknow character '%c'", curc));
            }
        }
        return Token.END;
    }

    private Token parseBool() {
        String str = curc == 'f' ? "false" : "true";
        int p = 1;
        StringBuilder buf = new StringBuilder().append(curc);
        while (p < str.length()) {
            buf.append(nextc());
            if (curc != str.charAt(p)) {
                throw new ParseJsonException("unknow charactor: " + buf.toString());
            }
            p++;
        }
        nextc();
        if (CharacterDef.symbolSet.contains(curc) || CharacterDef.blankChars.contains(curc) || curc == 0) {
            return Token.BOOL(str);
        } else {
            throw new ParseJsonException("unknow charactor: " + buf.toString());
        }
    }

    Token parseNull() {
        String nullStr = "null";
        int p = 1;
        StringBuilder buf = new StringBuilder().append(curc);
        while (p < nullStr.length()) {
            buf.append(nextc());
            if (curc != nullStr.charAt(p)) {
                throw new ParseJsonException("unknow charactor: " + buf.toString());
            }
            p++;
        }
        nextc();
        if (CharacterDef.symbolSet.contains(curc) || CharacterDef.blankChars.contains(curc) || curc == 0) {
            return Token.NULL;
        } else {
            throw new ParseJsonException("unknow charactor: " + buf.toString());
        }
    }

    Token parseString() {
        StringBuilder str = new StringBuilder();
        boolean escape = false;
        while (nextc() != 0) {
            if (escape) {
                if (CharacterDef.escapeMap.containsKey(curc)) {
                    str.append(CharacterDef.escapeMap.get(curc));
                    escape = false;
                } else {
                    throw new ParseJsonException(String.format("Invalid \\X enspace sequence '%c'", curc));
                }
            } else if (curc == '\\') {
                escape = true;
            } else if (curc == '"') {
                nextc();
                return Token.string(str.toString());
            } else {
                str.append(curc);
            }
        }
        throw new ParseJsonException("Invalid right character \"");
    }

    Token parseNum() {
        StringBuilder num = new StringBuilder().append(curc);
        boolean valid = true;
        boolean point = false;
        boolean negative = curc == '-';
        boolean zeroStart = curc == '0';
        if (negative || curc == '0') {
            valid = false;
        }

        if (negative) {
            num.append(nextc());
            if (isNum(curc)) {
                if (curc == '0') {
                    zeroStart = true;
                } else {
                    valid = true;
                }
            } else {
                throw new ParseJsonException(String.format("illegal number '%s'", num));
            }
        }

        while (true) {
            num.append(nextc());
            if (isNum(curc)) {
                if (zeroStart) {
                    valid = false;
                    break;
                } else {
                    valid = true;
                }
            } else if (curc == '.') {
                valid = false;
                if (point) {
                    break;
                }
                point = true;
            } else {
                if (CharacterDef.symbolSet.contains(curc) || CharacterDef.blankChars.contains(curc) || curc == 0) {
                    num.deleteCharAt(num.length() - 1);
                    break;
                } else {
                    valid = false;
                    break;
                }
            }
        }

        if (!valid) {
            throw new ParseJsonException(String.format("illegal number '%s'", num));
        }
        Token number = Token.number(num.toString());
        return number;
    }

    private char backc() {
        curc = loader.backc();
        return curc;
    }

    private char nextc() {
        curc = loader.nextc();
        return curc;
    }

    char nextNotBlankC() {
        curc = loader.nextNotBlankC();
        return curc;
    }

    private boolean isNum(int c) {
        return c <= '9' && c >= '0';
    }

    public List<Token> get() {
        return null;
    }

    public TokenIterator Iterator() {
        List<Token> tokenList = doParse();
        return new TokenIterator() {
            private int cursor = 0;
            private int lastRet = -1;

            @Override
            public boolean hasNext() {
                return cursor < tokenList.size();
            }

            @Override
            public Token next() {
                if (!hasNext()) {
                    return Token.END;
                }
                Token t = tokenList.get(cursor++);
                lastRet = cursor;
                return t;
            }

            @Override
            public Token peek() {
                return tokenList.get(cursor);
            }

            @Override
            public void remove() {
                if (lastRet < 0) {
                    throw new IllegalStateException();
                }
                tokenList.remove(lastRet);
                lastRet = -1;
            }
        };
    }

    private List<Token> doParse() {
        List<Token> tokens = new ArrayList<>();
        Token t;
        while ((t = parse()) != Token.END) {
            tokens.add(t);
        }
        return tokens;
    }
}
